package cn.jinbyte.web.filter;

import cn.jinbyte.core.utils.JsonUtils;
import cn.jinbyte.web.config.SqlInjectionProperties;
import cn.jinbyte.web.utils.PathMatcherUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.util.ContentCachingRequestWrapper;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * SQL注入防护过滤器，基于配置文件参数进行过滤
 */
public class SqlInjectionFilter implements Filter {

    private final SqlInjectionProperties sqlInjectionProperties;
    private Pattern sqlPattern;

    public SqlInjectionFilter(SqlInjectionProperties sqlInjectionProperties) {
        this.sqlInjectionProperties = sqlInjectionProperties;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化SQL注入模式正则表达式
        int flags = sqlInjectionProperties.isCaseInsensitive() ?
                Pattern.CASE_INSENSITIVE : 0;
        this.sqlPattern = Pattern.compile(sqlInjectionProperties.getPattern(), flags);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 如果SQL注入防护未启用，直接放行
        if (!sqlInjectionProperties.isEnabled()) {
            chain.doFilter(request, response);
            return;
        }

        // 检查是否为排除路径
        if (PathMatcherUtil.isExcluded(httpRequest, sqlInjectionProperties.getExcludePaths())) {
            chain.doFilter(request, response);
            return;
        }

        // 包装请求以便多次读取
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(httpRequest);

        // 检查请求参数
        if (hasSqlInjection(wrappedRequest)) {
            httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            try (PrintWriter writer = httpResponse.getWriter()) {
                writer.write("{\"code\":400,\"message\":\"可能存在SQL注入风险，请检查输入内容\"}");
            }
            return;
        }

        chain.doFilter(wrappedRequest, response);
    }

    /**
     * 检查请求中是否包含SQL注入模式
     */
    private boolean hasSqlInjection(ContentCachingRequestWrapper request) throws IOException {
        // 检查URL参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            if (isMatchPattern(entry.getKey()) || Arrays.stream(entry.getValue()).anyMatch(this::isMatchPattern)) {
                return true;
            }
        }

        // 检查请求体（JSON）
        String contentType = request.getContentType();
        if (contentType != null && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            byte[] content = request.getContentAsByteArray();
            if (content.length > 0) {
                String jsonContent = new String(content, request.getCharacterEncoding());
                if (isJsonContainSqlInjection(jsonContent)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 检查JSON内容是否包含SQL注入模式
     */
    private boolean isJsonContainSqlInjection(String jsonContent) {
        try {
            Object jsonObject = JsonUtils.parseJson(jsonContent);
            return checkJsonNode(jsonObject);
        } catch (Exception e) {
            // JSON解析失败时直接检查原始字符串
            return isMatchPattern(jsonContent);
        }
    }

    /**
     * 递归检查JSON节点
     */
    private boolean checkJsonNode(Object node) {
        if (node == null) {
            return false;
        }

        if (node instanceof String) {
            return isMatchPattern((String) node);
        }

        if (node instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) node;
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                if (entry.getKey() instanceof String && isMatchPattern((String) entry.getKey())) {
                    return true;
                }
                if (checkJsonNode(entry.getValue())) {
                    return true;
                }
            }
        }

        if (node instanceof List) {
            List<?> list = (List<?>) node;
            for (Object item : list) {
                if (checkJsonNode(item)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 检查字符串是否匹配SQL注入模式
     */
    private boolean isMatchPattern(String value) {
        return value != null && sqlPattern.matcher(value).matches();
    }
}

